#include "ConnectManager.h"

static Connect* connect_pool[MAX_CONNECT];
static Logger* connect_warning_logger;
static Thread thread_pool[MAX_THREAD];

static int lcs_add_event(int thread_index, int infd) {
    thread_pool[thread_index].event.data.fd = infd;
    thread_pool[thread_index].event.events = EPOLLIN | EPOLLET;
    if(epoll_ctl(thread_pool[thread_index].epoll_fd, EPOLL_CTL_ADD, infd, &(thread_pool[thread_index].event)) == -1) {
        connect_warning_logger->warning(connect_warning_logger, "[Worker thread]:Add infd failed, epoll_fd[%d]", thread_pool[thread_index].epoll_fd);
        return -1;
    }
    return 0;
}

static int lcs_init_event(int thread_index) {
    thread_pool[thread_index].epoll_fd = epoll_create(MAXEVENTS);
    if(thread_pool[thread_index].epoll_fd == -1) {
        connect_warning_logger->warning(connect_warning_logger, "[Worker thread]:Create epoll_fd failed", "");
        return -1;
    }

    thread_pool[thread_index].status = RUNNING;
    thread_pool[thread_index].events = malloc(sizeof(struct epoll_event)*MAXEVENTS);
    return 0;
}

static int lcs_destroy_event(int thread_index) {
    //todo
}

static void do_handle_connection(void* thread_pool_p) {
    int thread_index = *((int*)thread_pool_p);
    struct epoll_event* events = thread_pool[thread_index].events;
    int epoll_fd = thread_pool[thread_index].epoll_fd;

    while(1) {
        int i, n, cfd;
        n = epoll_wait(epoll_fd, events, MAXEVENTS, -1);

        /* blocking return ready fd */
        for(i = 0; i < n; i++) {
            cfd = events[i].data.fd;
            if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {
                connect_warning_logger->warning(connect_warning_logger, "[Worker thread]:Event Error, Event[%d]", events[i].events);
                close(events[i].data.fd);
            } else {
                unsigned int count;
                char socket_read_buf[MAX_BUF_SIZE];
                memset(socket_read_buf, 0, sizeof(char)*MAX_BUF_SIZE);
                count = read(cfd, socket_read_buf, MAX_BUF_SIZE);
                connect_warning_logger->warning(connect_warning_logger, "[Worker thread]Read count[%d]", count);
              
                if(0 == count) {
                    // todo
                    // no data
                    printf(socket_read_buf);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, cfd, &(thread_pool[thread_index].event));
                    close(cfd);
                    connect_warning_logger->warning(connect_warning_logger, "[Worker thread]:Client fd is shutdown[%d]", cfd);
                } else if(-1 == count) {
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, cfd, &(thread_pool[thread_index].event));
                    close(cfd);
                    connect_warning_logger->warning(connect_warning_logger, "[Worker thread]:Client fd have some error[%d]", cfd);
                } else {
                    socket_read_buf[count] = '\0';
                    printf(socket_read_buf);
                    char web_result[1024];
                    char *content = "Hello World";
                    char *html_response_template = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\nContent-Length: %d\r\nServer: Morysky LCS\r\n\r\n%s";
                    sprintf(web_result, html_response_template, strlen(content), content);
                    write(cfd, web_result, sizeof(web_result));
                    // read data
                }
            }
        }
    }
}

int handle_connection(int fd) {
    int thread_index = fd%MAX_THREAD;
    pthread_t thread;
    connect_warning_logger->warning(connect_warning_logger, "Accept Success, fd[%d]", fd);
    if(255 == thread_pool[thread_index].status) {
        lcs_init_event(thread_index);
        lcs_add_event(thread_index, fd);
        // create thread
        pthread_create(&thread, NULL, &do_handle_connection, (void*) &thread_index);
    } else {
        // add epoll event
        lcs_add_event(thread_index, fd);
    }
}

int create_and_bind(const char* port) {
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    result = rp = NULL;
    int ret, socket_fd;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = AI_PASSIVE;     /* All interfaces */

    ret = getaddrinfo(NULL, port, &hints, &result);
    if(0 != ret) {
        connect_warning_logger->warning(connect_warning_logger, "getaddrinfo error[%s]", gai_strerror(ret));
        return -1;
    }

    int found_valid_socket = 0;
    for(rp = result; rp != NULL; rp=rp->ai_next) {
        socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if(socket_fd == -1) {
            connect_warning_logger->warning(connect_warning_logger, "Error create socket", "");
            continue;
        }

        ret = bind(socket_fd, rp->ai_addr, rp->ai_addrlen);
        if(ret == 0) {
            // Bind success
            found_valid_socket = 1;
            break;
        }

        if(0 > ret) {
            connect_warning_logger->warning(connect_warning_logger, "Fail to bind[%s]", gai_strerror(ret));
        }
        ret = close(socket_fd);
        if(0 != ret) {
            connect_warning_logger->warning(connect_warning_logger, "Fail to close socket when bind failed", "");
            break;
        }
    }

    if(NULL == result) {
        connect_warning_logger->warning(connect_warning_logger, "Could not get valid address info [%s]", port);
        return -1;
    }

    freeaddrinfo(result);
    if(1 == found_valid_socket) {
        return socket_fd;
    } else {
        return -1;
    }
}

static int make_socket_non_blocking(int socket_fd) {
    int flags, ret;

    flags = fcntl(socket_fd, F_GETFL, 0);
    if(flags == -1) {
        connect_warning_logger->warning(connect_warning_logger, "Failed to call fcntl|F_GETFL", "");
        return -1;
    }

    flags |= O_NONBLOCK;
    ret = fcntl(socket_fd, F_SETFL, flags); /* write this fd non-block */
    if(ret == -1) {
        connect_warning_logger->warning(connect_warning_logger, "Failed to call fcntl|F_SETFL", "");
        return -1;
    }
    return 0;
}

int create_server(const char* port, int* myerrno, char* errmsg) {
    // init var
    int ret = -1;
    memset(thread_pool, -1, MAX_THREAD * sizeof(Thread));
    memset(connect_pool, -1, MAX_CONNECT * sizeof(Connect));

    // init logger
    connect_warning_logger = (Logger*)malloc(sizeof(Logger));
    char warning_file_name[MAX_LOG_FILENAME_LEN];
    memset(warning_file_name, '\0', MAX_LOG_FILENAME_LEN*sizeof(char));
    sprintf(warning_file_name, "%s/%s", CONNECT_LOG_PATH, CONNECT_LOG_WARNING_FILENAME);
    init_logger(connect_warning_logger, LOG_TYPE_FILE, warning_file_name);

    int socket_fd = create_and_bind(port);
    if(socket_fd == -1) {
        strcpy(errmsg, "Create and Bind Failed");
        return -1;
    }

    ret = make_socket_non_blocking(socket_fd);
    if(ret == -1) {
        strcpy(errmsg, "Make socket non blocking failed when creating server");
        return -1;
    }

    ret = listen(socket_fd, SOMAXCONN);
    if(ret == -1) {
        strcpy(errmsg, "Listen failed");
        return -1;
    }

    // Only the listening socket I/O event
    int epoll_fd = epoll_create(1);
    if(epoll_fd == -1) {
        strcpy(errmsg, "Epoll create failed");
        return -1;
    }

    struct epoll_event* events;
    struct epoll_event event;
    event.data.fd = socket_fd;
    event.events = EPOLLIN | EPOLLET;
    ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event); /* add socket to event queue */
    if(ret == -1) {
        strcpy(errmsg, "Add epoll_fd failed");
        return -1;
    }

    // allocate the memory
    events = calloc(1, sizeof(event));

    /* The event loop */
    while(1) {
        int n, i, cfd; // event fd index
        n = epoll_wait(epoll_fd, events, 1, -1);

        /* blocking return ready fd */
        for(i = 0; i < n; i++) {
            cfd = events[i].data.fd;
            if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {
                connect_warning_logger->warning(connect_warning_logger, "[Main thread]:Event Error, Event[%d]", events[i].events);
                close(events[i].data.fd);
            } else if(socket_fd == events[i].data.fd) {
                // Connection is coming
                while(1) {
                    struct sockaddr in_addr;
                    socklen_t in_len;
                    int infd;
                    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                    in_len = sizeof(in_addr);
                    // Add lock
                    // todo
                    infd = accept(socket_fd, &in_addr, &in_len);
                    if(infd == -1) {
                        connect_warning_logger->warning(connect_warning_logger, "Accept Failed, Errno[%d]", errno);
                        break;
                    }

                    // Save it in ConnectionPool, fd is the Key
                    Connect* new_conn = malloc(sizeof(Connect));
                    new_conn->status = 1;

                    if(infd > MAX_CONNECT-1) {
                        connect_warning_logger->warning(connect_warning_logger, "Reach max connection[%d], rejected", infd);
                        break;
                    }

                    connect_pool[infd] = new_conn;

                    ret = getnameinfo(&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);
                    if(ret == -1) {
                        connect_warning_logger->warning(connect_warning_logger, "Getnameinfo failed", "");
                        break;
                    }

                    /* Make the incoming socket non-blocking and add it to the
                     * list of fds to monitor. */
                    ret = make_socket_non_blocking(infd);
                    if(ret == -1) {
                        connect_warning_logger->warning(connect_warning_logger, "Make socket non blocking failed when accepting[%d]", infd);
                        break;
                    }

                    handle_connection(infd);

                    // Accept Finally Successfully
                    break;
                }
            } else {
                // Nothing
            }
        }
    }

    free(events);
    free(connect_warning_logger);

    // todo
    // free all connection/thread/events

    close(socket_fd);
    close(epoll_fd);
    return EXIT_SUCCESS;
}
